require 'datatypes/matrix.rb'
require 'datatypes/vector.rb'

# = Class ASTMat
# Inherits from ASTExpression. Represents the implementation of an AST especifically designed
# for matrix declarations
# == Attributes inherited from ASTExpression
# node, children
# == Methods inherited from ASTExpression
# check(), run(), to_s_children(im)
class ASTMat < ASTExpression
  
  # List of elements in the declaration of the matrix
  attr_reader :list
  # Type, can be mat, vec_col of vec_fil
  attr_reader :type
  
  # Constructor of the class
  # === Parameters
  # * list: List of elements in the declaration of the matrix
  def initialize(list)
    @list = list
  end

  # Method to evaluate the matrix
  # === Parameters
  # * parent_sym_table: Symbol table passed by the parent AST
  # * func_table: Function symbol table passed by the parent AST
  # === Return value
  # nil if executed with success or an error message
  def eval parent_sym_table, func_table
    msg = check_dynamic parent_sym_table, func_table
    if nil != msg
      return msg
    end

    # If there's nothing in it, it's {}
    if nil == @list
      @value = Vector.new([])
      @value.type = 'vec_fil'
      return nil
    end

    val = @list.value
    case @type
    when 'vec_fil'
      @value = Vector.new val[0]
      @value.type = 'vec_fil'

    when 'vec_col'
      vec = []
      for n_array in val
        vec.push(n_array[0])
      end
      @value = Vector.new(vec)
      @value.type = 'vec_col'

    when 'mat'
      mat = []
      if val[0][0].kind_of?Vector
        for lvec in val
          mat.push(lvec[0])
        end
      else
        for v in val
          vec = Vector.new(v)
          vec.type = 'vec_fil'
          mat.push(vec)
        end
      end
      @value = Matrix.new(mat)
    end
    return nil
  end
  
  # Method to check the dynamic correctitude of the matrix
  # === Parameters
  # * parent_sym_table: Symbol table passed by the parent AST
  # * func_table: Function symbol table passed by the pare
  # === Return value
  # nil if executed with success or an error message
  def check_dynamic parent_sym_table, func_table
    # If there's nothing in it, it's {}
    if nil == @list
      @type = 'vec_fil'
      return nil
    end
 
    msg = list.eval parent_sym_table, func_table
    if nil != msg
      return msg
    end

    val = @list.value

    # read check comments. parser ensures list lengths greater than zero
    if 1 == val.length

      # This is the case {N, N, ..., N}
      if val[0][0].kind_of?Float
        @type = 'vec_fil'
        return nil
      end

      # This is a 1-row matrix. Check ensures that if there are many elements,
      # they are of type num. In other case vec is allowed
      if (val[0][0].kind_of?Vector) && 'vec_fil' == val[0][0].type
        @type = 'mat'
        return nil
      end

      return "Tratando de crear matriz con '#{val[0][0].class.name}' (#{error_location})"
    end
 
    for elem in val

      # Now we can know if it was a vec_col or a vec_fil
      for subelem in elem
        if (subelem.kind_of?Vector) && 'vec_col' == subelem.type
          return "Tratando de crear matriz con 'vec_col' (#{subelem.error_location})"
        end
      end

      # check ensures that if elements above are num, all are num
      # check ensures that if elements above are vec, all are vec
      # check ensures that if elements above are vec, each list is of size 1
      # the previous iteration plus 'check' ensure all elements are vec_fil
      # parser ensures lists length greater than zero

      # Now we can check each vector size
      if (val[0][0].kind_of?Vector) && 'vec_fil' == val[0][0].type
        matrix_width = nil == matrix_width ? elem[0].size[0] : matrix_width
        if matrix_width != elem[0].size[0]
          return "Tratando de crear matriz con tamaños de filas distintos " +
                 "'#{matrix_width}', '#{elem[0].size[0]}' (#{error_location})"
        end
      end
    end
 
    # check ensures internal length of every row is the same 
    # parser ensures lists length greater than zero   
    if 1 == val[0].length && (val[0][0].kind_of?Float)
      # This is {N; N; ...; N} (row vector)
      @type = 'vec_col'
      return nil
    end

    # All other type validations have been done in check
    @type = 'mat'
    nil
  end

  # Method to check the static correctitude of the matrix
  # === Parameters
  # * parent_sym_table: Symbol table passed by the parent AST
  # * func_table: Function symbol table passed by the pare
  # * func_type: Type of valid return value
  # === Return value
  # nil if executed with success or an error message
  def check parent_sym_table, func_table, func_type    

    # If there's nothing in it, it's {}
    if nil == @list
      @type = 'vec'
      return nil
    end
 
    msg = list.check parent_sym_table, func_table, func_type
    if nil != msg
      return msg
    end
 
    if !@list.kind_of?ASTListSemiColon
      return "Error interno: La matriz es de tipo #{@list.class.name}, " +
             "se esperaba ASTListSemiColon (#{error_location})"
    end

    # parser ensures that if @list is not null, it is an ASTListSemiColon
    # with at least one ASTListComma
    if 1 == @list.children.length
      internal_list = @list.children[0].children
      internal_type = nil
 
      for elem in internal_list
        internal_type = nil == internal_type ? elem.type : internal_type
        if 1 < internal_list.length && 'num' != elem.type
          return "Tratando de crear un vector con '#{elem.type}' (#{elem.error_location})"
        end
      end
 
      if nil == internal_type
        return "Error interno: creando vector/matriz sin elementos (#{error_location})"
      end

      # We should actually set this to vec_fil, since this is the case {N, N, ..., N}
      if 'num' == internal_type
        @type = 'vec'
        return nil
      end

      # There were no elements in matrix
      # We should actually only accept row vectors, since this is a 1-row matrix
      if 'vec' == internal_type
        @type = 'mat'
        return nil
      end

      return "Tratando de crear vector/matriz con '#{internal_type}' (#{error_location})"
    end


    internal_length = nil
    for elem in @list.children
      # Each comma separated list must be of the same size
      internal_length = nil == internal_length ? elem.children.length : internal_length
      if internal_length != elem.children.length
        return "Tratando de crear matriz con filas de tamaños distintos " +
              "'#{elem.children.length}', '#{internal_length}' (#{elem.error_location})"
      end

      # Each element of each comma separated list must be of the same type
      for subelem in elem.children
        # internal_type is declared only in the first comma separated list.
        # The following lists must have elements of the very same type.
        internal_type = nil == internal_type ? subelem.type : internal_type
        if internal_type != subelem.type
          return "Tratando de crear " + ('num' == internal_type ? 'vector' : 'matriz') +
                 " con '#{subelem.type}' (#{subelem.error_location})"
        end
      end

      # Elements of comma separated list should be vector/matrix constructors
      # We actually shouldn't accept row vectors here.
      if 'num' != internal_type && 'vec' != internal_type
        return "Tratando de crear matriz con '#{internal_type}' (#{elem.error_location})"
      end

      # Vector comma separated list should contain only one element
      # This is equal for row or column vectors.
      if 'vec' == internal_type && 1 < elem.children.length
        return "Tratando de crear matriz '#{elem.children.length}' vectores " +
               "por fila (debería ser 1) (#{elem.error_location})"
      end

    end
    
    if 1 == internal_length
      if 'num' == internal_type
        # We should be assigning vec_col, this is {N; N; ...; N} (row vector)
        @type = 'vec'
      else
        @type = 'mat'
      end
      return nil
    end

    if 1 < internal_length && 'num' == internal_type
      @type = 'mat'
      return nil
    end

    return "Tratando de crear matriz con filas de '#{length}' vectores (#{error_location})"
  end

  # Returns the closest wrong token to the place were an error is generated
  def error_tk
    @list.error_tk
  end

  # Creates a 'String' representation of the AST with 'i' identation spaces
  # === Parameters
  # * i: Identation with which the AST should be printed with
  def to_s(i)
    im = i + @@c
    output = "(Matriz\n#{im}#{@list.to_s(im)}\n#{i})"
  end
end